using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using HQLAddin.Highlighting;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio;
using System.Runtime.InteropServices;

namespace HQLAddin.Intellisense
{

	//This code was extacted from the Pkgdef example. IT is way overconvoluted.
	//TODO enhance this.

	#region Command Filter

	[Export(typeof(IVsTextViewCreationListener))]
	[ContentType("text")]
	[TextViewRole(PredefinedTextViewRoles.Interactive)]
	[Order(After = "default")]
	internal sealed class VsTextViewCreationListener : IVsTextViewCreationListener
	{
		[Import]
		IVsEditorAdaptersFactoryService adaptersFactory = null;

		[Import]
		ICompletionBroker completionBroker = null;

		public void VsTextViewCreated(IVsTextView textViewAdapter)
		{
			var view = adaptersFactory.GetWpfTextView(textViewAdapter);
			var filter = new CommandFilter(view, completionBroker);

			IOleCommandTarget next;
			textViewAdapter.AddCommandFilter(filter, out next);
			filter.Next = next;
		}
	}
	
	

	/// <summary>
	/// A command filter that watches for the typing of a '$' to start a completion session
	/// </summary>
	internal sealed class CommandFilter : IOleCommandTarget
	{
		ICompletionSession currentSession;

		public CommandFilter(IWpfTextView textView, ICompletionBroker broker)
		{
			currentSession = null;

			TextView = textView;
			Broker = broker;
		}

		public IWpfTextView TextView { get; private set; }
		public ICompletionBroker Broker { get; private set; }
		public IOleCommandTarget Next { get; set; }

		private static char GetTypeChar(IntPtr pvaIn)
		{
			return (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
		}

		public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
		{
			

			bool handled = false;
			bool updateFilter = false;
			bool previousSessionActive = currentSession != null && !currentSession.IsDismissed;
			int hresult = VSConstants.S_OK;

			var point = TextView.Caret.Position.Point.GetPoint(TextView.TextSnapshot, PositionAffinity.Successor);

			if (point.HasValue && point.Value.IsInQueryTag())
			{
				// 1. Pre-process
				if (pguidCmdGroup == VSConstants.VSStd2K)
				{
					switch ((VSConstants.VSStd2KCmdID) nCmdID)
					{
						case VSConstants.VSStd2KCmdID.AUTOCOMPLETE:
						case VSConstants.VSStd2KCmdID.COMPLETEWORD:
							handled = StartSession();
							break;
						case VSConstants.VSStd2KCmdID.RETURN:
							handled = Complete(false);
							break;
						case VSConstants.VSStd2KCmdID.TAB:
							handled = Complete(true);
							break;
						case VSConstants.VSStd2KCmdID.BACKSPACE:
							updateFilter = true;
							break;
						case VSConstants.VSStd2KCmdID.CANCEL:
							handled = Cancel();
							break;
						case VSConstants.VSStd2KCmdID.TYPECHAR:
							char ch = GetTypeChar(pvaIn);
							if(ch == '.' || ch == ' ')  Complete(true);
							break;
					}
				}
			}


			if (!handled)
			{
				hresult = Next.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);

				// update the filter after processing the key
				if (updateFilter)
					Filter();
			}

			if (ErrorHandler.Succeeded(hresult))
			{
				if (pguidCmdGroup == VSConstants.VSStd2K)
				{
					switch ((VSConstants.VSStd2KCmdID)nCmdID)
					{
						case VSConstants.VSStd2KCmdID.TYPECHAR:
							char ch = GetTypeChar(pvaIn);
							if (currentSession != null && !currentSession.IsDismissed)
								Filter();
							else if (ch == '.')
								StartSession();
							break;
					}
				}
			}

			return hresult;
		}

		private void Filter()
		{
			if (currentSession == null || currentSession.IsDismissed)
				return;

			currentSession.SelectedCompletionSet.Filter();
			currentSession.SelectedCompletionSet.SelectBestMatch();
			currentSession.SelectedCompletionSet.Recalculate();
		}

		bool Cancel()
		{
			if (currentSession == null)
				return false;

			currentSession.Dismiss();
			currentSession = null;
			return true;
		}

		bool Complete(bool force)
		{
			if (currentSession == null || currentSession.IsDismissed)
				return false;

			if (!currentSession.SelectedCompletionSet.SelectionStatus.IsSelected && !force)
			{
				currentSession.Dismiss();
				return false;
			}
			else
			{
				currentSession.Commit();
				return true;
			}
		}

		bool StartSession()
		{
			if (currentSession != null && !currentSession.IsDismissed)
				return false;

			SnapshotPoint caret = TextView.Caret.Position.BufferPosition;
			ITextSnapshot snapshot = caret.Snapshot;

			currentSession = Broker.CreateCompletionSession(TextView, snapshot.CreateTrackingPoint(caret, PointTrackingMode.Positive), true);
			currentSession.Start();

			currentSession.Dismissed += (sender, args) => currentSession = null;

			return true;
		}

		public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
		{
			if (pguidCmdGroup == VSConstants.VSStd2K)
			{
				switch ((VSConstants.VSStd2KCmdID)prgCmds[0].cmdID)
				{
					case VSConstants.VSStd2KCmdID.AUTOCOMPLETE:
					case VSConstants.VSStd2KCmdID.COMPLETEWORD:
						prgCmds[0].cmdf = (uint)OLECMDF.OLECMDF_ENABLED | (uint)OLECMDF.OLECMDF_SUPPORTED;
						return VSConstants.S_OK;
				}
			}
			return Next.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText);
		}
	}

	#endregion
}
